﻿/*
Copyright (c) 2009 Vratislav Cermak (vratacermak@gmail.com)
 
This file is part of TeachMe Project. See http://code.google.com/p/teach-me for more information.
 
TeachMe is an open source project. Using of TeachMe is free of charge.
It is distributed under GNU General Public License version 3 (GPLv3).
GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
 
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using TeachMe.Application.Events;
using TeachMe.Application.Win32;

namespace TeachMe.Application
{
    public class MouseManager : IDisposable
    {
        private class PressedMouseDescriptor
        {
            public EntryDescriptor EntryDescriptor { get; set; }
            public EntryButtonType EntryButtonType { get; set; }
            public Point AbsoluteLocation { get; set; }
        }

        public event EventHandler<EntryEventArgs> EntryEvent;
        public event EventHandler<EntryClickedEventArgs> EntryClicked;
        public event EventHandler<EntryEventArgs> EntryMouseWheel;
        public event EventHandler<EntryButtonEventArgs> EntryButtonEvent;
        public event EventHandler<EntryButtonPressedEventArgs> EntryButtonPressed;
        public event EventHandler<EntryButtonClickedEventArgs> EntryButtonClicked;
        public event EventHandler<MovingEventArgs> Moving;
        public event EventHandler<MovingStartedEventArgs> MovingStarted;
        public event EventHandler<EventArgs> MovingStopped;

        private readonly Dictionary<MouseButtons, PressedMouseDescriptor> _pressedMice;
        private readonly MouseHook _mouseHook;
        private readonly DisplayManager _displayManager;
        private bool _disposed;
        private EntryDescriptor _currentEntry;
        private EntryButtonType _currentButton;
        private bool _moving;
        private EntryButtonType _pressedButton;
        private EntryDescriptor _entryOfPressedByutton;

        public void StopMoving()
        {
            FireMovingStopped();

            _moving = false;
            _pressedMice.Remove(MouseButtons.Left);
        }

        public bool Enabled
        {
            get
            {
                return _mouseHook.Enabled;
            }
            set
            {
                if (value != _mouseHook.Enabled)
                {
                    Logging.Debug(GetType(), "Hooks are {0}.", value ? "enabled" : "disabled");

                    _mouseHook.Enabled = value;
                }
            }
        }

        public MouseManager(DisplayManager displayManager)
        {
            _displayManager = displayManager;
            _mouseHook = MouseHook.Initialize();
            _mouseHook.MouseMove += OnMouseMove;
            _mouseHook.MouseUp += OnMouseUp;
            _mouseHook.MouseDown += OnMouseDown;
            _mouseHook.MouseWheel += OnMouseWheel;

            _pressedMice = new Dictionary<MouseButtons, PressedMouseDescriptor>();
        }

        public void Refresh()
        {
            if (_currentEntry != null)
            {
                if (_currentButton != EntryButtonType.Unknown)
                {
                    FireButtonEvent(EntryButtonEventType.Leave);
                }

                FireEntryEvent(EntryEventType.Leave);
            }

            _currentButton = EntryButtonType.Unknown;
            _currentEntry = _displayManager.GetEntryDescriptorOnLocation(Control.MousePosition);
            
            if (_currentEntry != null)
            {
                FireEntryEvent(EntryEventType.Enter);

                _currentEntry.GetButton(Control.MousePosition, out _currentButton);

                if (_currentButton != EntryButtonType.Unknown)
                {
                    FireButtonEvent(EntryButtonEventType.Enter);
                }
            }
        }

        public void Dispose()
        {
            if (_disposed)
            {
                return;
            }
            
            _mouseHook.Dispose();
            _disposed = true;
        }

        void OnMouseDown(object sender, MouseEventArgs e)
        {
            if (_currentEntry != null)
            {
                if (_currentButton != EntryButtonType.Unknown && e.Button == MouseButtons.Left)
                {
                    // button can be pressed only by left mouse

                    _pressedButton = _currentButton;
                    _entryOfPressedByutton = _currentEntry;

                    FireButtonPressedEvent(e.Button);
                }

                if (_pressedMice.ContainsKey(e.Button))
                {
                    Logging.Error(GetType(), "'{0}' is already pressed.", e.Button);
                }
                else
                {
                    _pressedMice.Add(e.Button, new PressedMouseDescriptor { AbsoluteLocation = e.Location, EntryButtonType = _currentButton, EntryDescriptor = _currentEntry });
                }
            }
        }

        void OnMouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && _moving)
            {
                FireMovingStopped();

                _pressedMice.Remove(MouseButtons.Left);
                _moving = false;

                return;
            }

            if (_pressedButton != EntryButtonType.Unknown)
            {
                _pressedButton = EntryButtonType.Unknown;
                _entryOfPressedByutton = null;
            }

            if (_pressedMice.ContainsKey(e.Button))
            {
                if (_pressedMice[e.Button].EntryDescriptor == _currentEntry)
                {
                    if (_pressedMice[e.Button].EntryButtonType != EntryButtonType.Unknown)
                    {
                        if (_pressedMice[e.Button].EntryButtonType == _currentButton)
                        {
                            FireButtonClickedEvent(e.Button);
                        }
                    }
                    else
                    {
                        FireEntryClickedEvent(e.Button);
                    }
                }

                _pressedMice.Remove(e.Button);
            }
        }

        void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (_pressedMice.ContainsKey(MouseButtons.Left) && _pressedMice[MouseButtons.Left].EntryButtonType == EntryButtonType.Unknown)
            {
                if (_moving || Math.Abs(_pressedMice[MouseButtons.Left].AbsoluteLocation.X - e.Location.X) > 2 || Math.Abs(_pressedMice[MouseButtons.Left].AbsoluteLocation.Y - e.Location.Y) > 2)
                {
                    if (!_moving)
                    {
                        FireMovingStarted();

                        _moving = true;
                    }

                    FireMovingEvent(_pressedMice[MouseButtons.Left].AbsoluteLocation, e.Location);

                    return;
                }
            }

            EntryDescriptor entryDescriptor = _displayManager.GetEntryDescriptorOnLocation(e.Location);

            if (entryDescriptor == null)
            {
                if (_currentButton != EntryButtonType.Unknown)
                {
                    FireButtonEvent(EntryButtonEventType.Leave);
                }
                if (_currentEntry != null)
                {
                    FireEntryEvent(EntryEventType.Leave);
                }
                
                _currentButton = EntryButtonType.Unknown;
                _currentEntry = null;

                return;
            }

            if (entryDescriptor != _currentEntry)
            {
                if (_currentEntry != null)
                {
                    if (_currentButton != EntryButtonType.Unknown)
                    {
                        FireButtonEvent(EntryButtonEventType.Leave);
                    }

                    FireEntryEvent(EntryEventType.Leave);
                }

                //
                
                _currentEntry = entryDescriptor;

                FireEntryEvent(EntryEventType.Enter);

                if (entryDescriptor.GetButton(e.Location, out _currentButton))
                {
                    FireButtonEvent(EntryButtonEventType.Enter);
                }
            }
            else
            {
                EntryButtonType entryButton;

                entryDescriptor.GetButton(e.Location, out entryButton);

                if (_currentButton != EntryButtonType.Unknown && _currentButton != entryButton && entryButton != EntryButtonType.Unknown)
                {
                    FireButtonEvent(EntryButtonEventType.Leave);

                    _currentButton = entryButton;

                    FireButtonEvent(EntryButtonEventType.Enter);
                }
                else if (_currentButton != EntryButtonType.Unknown && entryButton == EntryButtonType.Unknown)
                {
                    FireButtonEvent(EntryButtonEventType.Leave);

                    _currentButton = EntryButtonType.Unknown;
                }
                else if (_currentButton == EntryButtonType.Unknown && entryButton != EntryButtonType.Unknown)
                {
                    _currentButton = entryButton;

                    FireButtonEvent(EntryButtonEventType.Enter);
                }
            }
        }

        void OnMouseWheel(object sender, MouseEventArgs e)
        {
            if (_currentEntry != null && _pressedMice.Count == 0)
            {
                FireEntryMouseWheel();
            }
        }

        private void FireEntryEvent(EntryEventType eventType)
        {
            EventHandler<EntryEventArgs> handler = EntryEvent;
            if (handler != null)
            {
                handler(this, new EntryEventArgs { EntryDescriptor = _currentEntry, EventType = eventType });
            }
        }

        private void FireEntryMouseWheel()
        {
            EventHandler<EntryEventArgs> handler = EntryMouseWheel;
            if (handler != null)
            {
                handler(this, new EntryEventArgs { EntryDescriptor = _currentEntry, EventType = EntryEventType.None});
            }
        }

        private void FireEntryClickedEvent(MouseButtons mouseButtons)
        {
            EventHandler<EntryClickedEventArgs> handler = EntryClicked;
            if (handler != null)
            {
                handler(this, new EntryClickedEventArgs { EntryDescriptor = _currentEntry, MouseButtons = mouseButtons });
            }
        }

        private void FireButtonEvent(EntryButtonEventType eventType)
        {
            if (_pressedButton != EntryButtonType.Unknown && (_pressedButton != _currentButton || _entryOfPressedByutton != _currentEntry))
            {
                // if button is pressed, fire only events for already pressed button
                return;
            }

            EventHandler<EntryButtonEventArgs> handler = EntryButtonEvent;
            if (handler != null)
            {
                handler(this, new EntryButtonEventArgs { EntryDescriptor = _currentEntry, ButtonType = _currentButton, EventType = eventType, Pressed = _pressedButton != EntryButtonType.Unknown });
            }
        }

        private void FireButtonClickedEvent(MouseButtons mouseButtons)
        {
            EventHandler<EntryButtonClickedEventArgs> handler = EntryButtonClicked;
            if (handler != null)
            {
                handler(this, new EntryButtonClickedEventArgs { EntryDescriptor = _currentEntry, ButtonType = _currentButton, MouseButtons = mouseButtons });
            }
        }

        private void FireButtonPressedEvent(MouseButtons mouseButtons)
        {
            EventHandler<EntryButtonPressedEventArgs> handler = EntryButtonPressed;
            if (handler != null)
            {
                handler(this, new EntryButtonPressedEventArgs { EntryDescriptor = _currentEntry, ButtonType = _currentButton, MouseButtons = mouseButtons });
            }
        }

        private void FireMovingEvent(Point originalLocation, Point currentLocation)
        {
            EventHandler<MovingEventArgs> handler = Moving;
            if (handler != null)
            {
                handler(this, new MovingEventArgs{CurrentLocation =  currentLocation, StartLocation = originalLocation});
            }
        }

        private void FireMovingStarted()
        {
            EventHandler<MovingStartedEventArgs> handler = MovingStarted;
            if (handler != null)
            {
                handler(this, new MovingStartedEventArgs{EntryDescriptor = _currentEntry});
            }
        }

        private void FireMovingStopped()
        {
            EventHandler<EventArgs> handler = MovingStopped;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
    }
}
